/* 动态 SQL 支持例程
 *
 * src/interfaces/ecpg/ecpglib/descriptor.c
 */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include "catalog/pg_type_d.h"
#include "ecpg-pthread-win32.h"
#include "ecpgerrno.h"
#include "ecpglib.h"
#include "ecpglib_extern.h"
#include "ecpgtype.h"
#include "sql3types.h"
#include "sqlca.h"
#include "sqlda.h"

static void fc_descriptor_free(struct descriptor *fc_desc);

/* 我们为每个线程单独管理描述符。 */
#ifdef ENABLE_THREAD_SAFETY
static pthread_key_t descriptor_key;
static pthread_once_t descriptor_once = PTHREAD_ONCE_INIT;

static void fc_descriptor_deallocate_all(struct descriptor *fc_list);

static void fc_descriptor_destructor(void *fc_arg)
{
	fc_descriptor_deallocate_all(fc_arg);
}

static void fc_descriptor_key_init(void)
{
	pthread_key_create(&descriptor_key, fc_descriptor_destructor);
}

static struct descriptor *
fc_get_descriptors(void)
{
	pthread_once(&descriptor_once, fc_descriptor_key_init);
	return (struct descriptor *) pthread_getspecific(descriptor_key);
}

static void fc_set_descriptors(struct descriptor *fc_value)
{
	pthread_setspecific(descriptor_key, fc_value);
}
#else
static struct descriptor *all_descriptors = NULL;

#define get_descriptors()		(all_descriptors)
#define set_descriptors(value)	do { all_descriptors = (value); } while(0)
#endif

/* 旧的内部便捷函数，可能会在以后去掉 */
static PGresult * fc_ecpg_result_by_descriptor(int fc_line, const char *fc_name)
{
	struct descriptor *fc_desc = ecpg_find_desc(fc_line, fc_name);

	if (fc_desc == NULL)
		return NULL;
	return fc_desc->result;
}

static unsigned int fc_ecpg_dynamic_type_DDT(Oid fc_type)
{
	switch (fc_type)
	{
		case DATEOID:
			return SQL3_DDT_DATE;
		case TIMEOID:
			return SQL3_DDT_TIME;
		case TIMESTAMPOID:
			return SQL3_DDT_TIMESTAMP;
		case TIMESTAMPTZOID:
			return SQL3_DDT_TIMESTAMP_WITH_TIME_ZONE;
		case TIMETZOID:
			return SQL3_DDT_TIME_WITH_TIME_ZONE;
		default:
			return SQL3_DDT_ILLEGAL;
	}
}

bool ECPGget_desc_header(int fc_lineno, const char *fc_desc_name, int *fc_count)
{
	PGresult   *fc_ECPGresult;
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}

	ecpg_init_sqlca(fc_sqlca);
	fc_ECPGresult = fc_ecpg_result_by_descriptor(fc_lineno, fc_desc_name);
	if (!fc_ECPGresult)
		return false;

	*fc_count = PQnfields(fc_ECPGresult);
	fc_sqlca->sqlerrd[2] = 1;
	ecpg_log("ECPGget_desc_header: found %d attributes\n", *fc_count);
	return true;
}

static bool fc_get_int_item(int fc_lineno, void *fc_var, enum ECPGttype fc_vartype, int fc_value)
{
	switch (fc_vartype)
	{
		case ECPGt_short:
			*(short *) fc_var = (short) fc_value;
			break;
		case ECPGt_int:
			*(int *) fc_var = (int) fc_value;
			break;
		case ECPGt_long:
			*(long *) fc_var = (long) fc_value;
			break;
		case ECPGt_unsigned_short:
			*(unsigned short *) fc_var = (unsigned short) fc_value;
			break;
		case ECPGt_unsigned_int:
			*(unsigned int *) fc_var = (unsigned int) fc_value;
			break;
		case ECPGt_unsigned_long:
			*(unsigned long *) fc_var = (unsigned long) fc_value;
			break;
		case ECPGt_long_long:
			*(long long int *) fc_var = (long long int) fc_value;
			break;
		case ECPGt_unsigned_long_long:
			*(unsigned long long int *) fc_var = (unsigned long long int) fc_value;
			break;
		case ECPGt_float:
			*(float *) fc_var = (float) fc_value;
			break;
		case ECPGt_double:
			*(double *) fc_var = (double) fc_value;
			break;
		default:
			ecpg_raise(fc_lineno, ECPG_VAR_NOT_NUMERIC, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
			return false;
	}

	return true;
}

static bool fc_set_int_item(int fc_lineno, int *fc_target, const void *fc_var, enum ECPGttype fc_vartype)
{
	switch (fc_vartype)
	{
		case ECPGt_short:
			*fc_target = *(const short *) fc_var;
			break;
		case ECPGt_int:
			*fc_target = *(const int *) fc_var;
			break;
		case ECPGt_long:
			*fc_target = *(const long *) fc_var;
			break;
		case ECPGt_unsigned_short:
			*fc_target = *(const unsigned short *) fc_var;
			break;
		case ECPGt_unsigned_int:
			*fc_target = *(const unsigned int *) fc_var;
			break;
		case ECPGt_unsigned_long:
			*fc_target = *(const unsigned long *) fc_var;
			break;
		case ECPGt_long_long:
			*fc_target = *(const long long int *) fc_var;
			break;
		case ECPGt_unsigned_long_long:
			*fc_target = *(const unsigned long long int *) fc_var;
			break;
		case ECPGt_float:
			*fc_target = *(const float *) fc_var;
			break;
		case ECPGt_double:
			*fc_target = *(const double *) fc_var;
			break;
		default:
			ecpg_raise(fc_lineno, ECPG_VAR_NOT_NUMERIC, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
			return false;
	}

	return true;
}

static bool fc_get_char_item(int fc_lineno, void *fc_var, enum ECPGttype fc_vartype, char *fc_value, int fc_varcharsize)
{
	switch (fc_vartype)
	{
		case ECPGt_char:
		case ECPGt_unsigned_char:
		case ECPGt_string:
			strncpy((char *) fc_var, fc_value, fc_varcharsize);
			break;
		case ECPGt_varchar:
			{
				struct ECPGgeneric_varchar *fc_variable =
				(struct ECPGgeneric_varchar *) fc_var;

				if (fc_varcharsize == 0)
					memcpy(fc_variable->arr, fc_value, strlen(fc_value));
				else
					strncpy(fc_variable->arr, fc_value, fc_varcharsize);

				fc_variable->len = strlen(fc_value);
				if (fc_varcharsize > 0 && fc_variable->len > fc_varcharsize)
					fc_variable->len = fc_varcharsize;
			}
			break;
		default:
			ecpg_raise(fc_lineno, ECPG_VAR_NOT_CHAR, ECPG_SQLSTATE_RESTRICTED_DATA_TYPE_ATTRIBUTE_VIOLATION, NULL);
			return false;
	}

	return true;
}

#define RETURN_IF_NO_DATA	if (fc_ntuples < 1) \
				{ \
					va_end(fc_args); \
					ecpg_raise(fc_lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL); \
					return false; \
				}

bool ECPGget_desc(int fc_lineno, const char *fc_desc_name, int fc_index,...)
{
	va_list		fc_args;
	PGresult   *fc_ECPGresult;
	enum ECPGdtype fc_type;
	int			fc_ntuples,
				fc_act_tuple;
	struct variable fc_data_var;
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_lineno, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}

	va_start(fc_args, fc_index);
	ecpg_init_sqlca(fc_sqlca);
	fc_ECPGresult = fc_ecpg_result_by_descriptor(fc_lineno, fc_desc_name);
	if (!fc_ECPGresult)
	{
		va_end(fc_args);
		return false;
	}

	fc_ntuples = PQntuples(fc_ECPGresult);

	if (fc_index < 1 || fc_index > PQnfields(fc_ECPGresult))
	{
		ecpg_raise(fc_lineno, ECPG_INVALID_DESCRIPTOR_INDEX, ECPG_SQLSTATE_INVALID_DESCRIPTOR_INDEX, NULL);
		va_end(fc_args);
		return false;
	}

	ecpg_log("ECPGget_desc: reading items for tuple %d\n", fc_index);
	--fc_index;

	fc_type = va_arg(fc_args, enum ECPGdtype);

	memset(&fc_data_var, 0, sizeof fc_data_var);
	fc_data_var.type = ECPGt_EORT;
	fc_data_var.ind_type = ECPGt_NO_INDICATOR;

	while (fc_type != ECPGd_EODT)
	{
		char		fc_type_str[20];
		long		fc_varcharsize;
		long		fc_offset;
		long		fc_arrsize;
		enum ECPGttype fc_vartype;
		void	   *fc_var;

		fc_vartype = va_arg(fc_args, enum ECPGttype);
		fc_var = va_arg(fc_args, void *);
		fc_varcharsize = va_arg(fc_args, long);
		fc_arrsize = va_arg(fc_args, long);
		fc_offset = va_arg(fc_args, long);

		switch (fc_type)
		{
			case (ECPGd_indicator):
				RETURN_IF_NO_DATA;
				fc_data_var.ind_type = fc_vartype;
				fc_data_var.ind_pointer = fc_var;
				fc_data_var.ind_varcharsize = fc_varcharsize;
				fc_data_var.ind_arrsize = fc_arrsize;
				fc_data_var.ind_offset = fc_offset;
				if (fc_data_var.ind_arrsize == 0 || fc_data_var.ind_varcharsize == 0)
					fc_data_var.ind_value = *((void **) (fc_data_var.ind_pointer));
				else
					fc_data_var.ind_value = fc_data_var.ind_pointer;
				break;

			case ECPGd_data:
				RETURN_IF_NO_DATA;
				fc_data_var.type = fc_vartype;
				fc_data_var.pointer = fc_var;
				fc_data_var.varcharsize = fc_varcharsize;
				fc_data_var.arrsize = fc_arrsize;
				fc_data_var.offset = fc_offset;
				if (fc_data_var.arrsize == 0 || fc_data_var.varcharsize == 0)
					fc_data_var.value = *((void **) (fc_data_var.pointer));
				else
					fc_data_var.value = fc_data_var.pointer;
				break;

			case ECPGd_name:
				if (!fc_get_char_item(fc_lineno, fc_var, fc_vartype, PQfname(fc_ECPGresult, fc_index), fc_varcharsize))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: NAME = %s\n", PQfname(fc_ECPGresult, fc_index));
				break;

			case ECPGd_nullable:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, 1))
				{
					va_end(fc_args);
					return false;
				}

				break;

			case ECPGd_key_member:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, 0))
				{
					va_end(fc_args);
					return false;
				}

				break;

			case ECPGd_scale:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, (PQfmod(fc_ECPGresult, fc_index) - VARHDRSZ) & 0xffff))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: SCALE = %d\n", (PQfmod(fc_ECPGresult, fc_index) - VARHDRSZ) & 0xffff);
				break;

			case ECPGd_precision:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, PQfmod(fc_ECPGresult, fc_index) >> 16))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: PRECISION = %d\n", PQfmod(fc_ECPGresult, fc_index) >> 16);
				break;

			case ECPGd_octet:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, PQfsize(fc_ECPGresult, fc_index)))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: OCTET_LENGTH = %d\n", PQfsize(fc_ECPGresult, fc_index));
				break;

			case ECPGd_length:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, PQfmod(fc_ECPGresult, fc_index) - VARHDRSZ))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: LENGTH = %d\n", PQfmod(fc_ECPGresult, fc_index) - VARHDRSZ);
				break;

			case ECPGd_type:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, ecpg_dynamic_type(PQftype(fc_ECPGresult, fc_index))))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: TYPE = %d\n", ecpg_dynamic_type(PQftype(fc_ECPGresult, fc_index)));
				break;

			case ECPGd_di_code:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, fc_ecpg_dynamic_type_DDT(PQftype(fc_ECPGresult, fc_index))))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: TYPE = %d\n", fc_ecpg_dynamic_type_DDT(PQftype(fc_ECPGresult, fc_index)));
				break;

			case ECPGd_cardinality:
				if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, PQntuples(fc_ECPGresult)))
				{
					va_end(fc_args);
					return false;
				}

				ecpg_log("ECPGget_desc: CARDINALITY = %d\n", PQntuples(fc_ECPGresult));
				break;

			case ECPGd_ret_length:
			case ECPGd_ret_octet:

				RETURN_IF_NO_DATA;

				/*
				 * 这像是 ECPGstore_result
				 */
				if (fc_arrsize > 0 && fc_ntuples > fc_arrsize)
				{
					ecpg_log("ECPGget_desc on line %d: incorrect number of matches; %d don't fit into array of %ld\n",
							 fc_lineno, fc_ntuples, fc_arrsize);
					ecpg_raise(fc_lineno, ECPG_TOO_MANY_MATCHES, ECPG_SQLSTATE_CARDINALITY_VIOLATION, NULL);
					va_end(fc_args);
					return false;
				}
				/* 如果需要，分配存储空间 */
				if (fc_arrsize == 0 && *(void **) fc_var == NULL)
				{
					void	   *fc_mem = (void *) ecpg_auto_alloc(fc_offset * fc_ntuples, fc_lineno);

					if (!fc_mem)
					{
						va_end(fc_args);
						return false;
					}
					*(void **) fc_var = fc_mem;
					fc_var = fc_mem;
				}

				for (fc_act_tuple = 0; fc_act_tuple < fc_ntuples; fc_act_tuple++)
				{
					if (!fc_get_int_item(fc_lineno, fc_var, fc_vartype, PQgetlength(fc_ECPGresult, fc_act_tuple, fc_index)))
					{
						va_end(fc_args);
						return false;
					}
					fc_var = (char *) fc_var + fc_offset;
					ecpg_log("ECPGget_desc: RETURNED[%d] = %d\n", fc_act_tuple, PQgetlength(fc_ECPGresult, fc_act_tuple, fc_index));
				}
				break;

			default:
				snprintf(fc_type_str, sizeof(fc_type_str), "%d", fc_type);
				ecpg_raise(fc_lineno, ECPG_UNKNOWN_DESCRIPTOR_ITEM, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, fc_type_str);
				va_end(fc_args);
				return false;
		}

		fc_type = va_arg(fc_args, enum ECPGdtype);
	}

	if (fc_data_var.type != ECPGt_EORT)
	{
		struct statement fc_stmt;

		memset(&fc_stmt, 0, sizeof fc_stmt);
		fc_stmt.lineno = fc_lineno;

		/* 确保我们不考虑数字输入的区域设置 */
		/* 因为数据库给出了标准小数点 */
		/* （见 execute.c 中的注释） */
#ifdef HAVE_USELOCALE

		/*
		 * 要到这里，上面的 PQnfields() 测试必须找到非零
		 * 字段。需要一个连接来创建这样的描述符。（EXEC
		 * SQL SET DESCRIPTOR 可以填充描述符的“项目”，但它
		 * 不能改变描述符的 PQnfields()。）任何成功的
		 * 连接都会初始化 ecpg_clocale。
		 */
		Assert(ecpg_clocale);
		fc_stmt.oldlocale = uselocale(ecpg_clocale);
#else
#ifdef HAVE__CONFIGTHREADLOCALE
		fc_stmt.oldthreadlocale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
#endif
		fc_stmt.oldlocale = ecpg_strdup(setlocale(LC_NUMERIC, NULL), fc_lineno);
		setlocale(LC_NUMERIC, "C");
#endif

		/* 拼命尝试猜测一些合理的东西 */
		fc_stmt.connection = ecpg_get_connection(NULL);
		ecpg_store_result(fc_ECPGresult, fc_index, &fc_stmt, &fc_data_var);

#ifdef HAVE_USELOCALE
		if (fc_stmt.oldlocale != (locale_t) 0)
			uselocale(fc_stmt.oldlocale);
#else
		if (fc_stmt.oldlocale)
		{
			setlocale(LC_NUMERIC, fc_stmt.oldlocale);
			ecpg_free(fc_stmt.oldlocale);
		}
#ifdef HAVE__CONFIGTHREADLOCALE
		if (fc_stmt.oldthreadlocale != -1)
			(void) _configthreadlocale(fc_stmt.oldthreadlocale);
#endif
#endif
	}
	else if (fc_data_var.ind_type != ECPGt_NO_INDICATOR && fc_data_var.ind_pointer != NULL)

		/*
		 * ind_type != NO_INDICATOR 应始终具有 ind_pointer != NULL，
		 * 但由于这可能在 .c 文件中手动更改，因此我们还是谨慎处理
		 */
	{
		/*
		 * 这像是 ECPGstore_result，但由于我们手头没有数据
		 * 变量，所以无法调用它
		 */
		if (fc_data_var.ind_arrsize > 0 && fc_ntuples > fc_data_var.ind_arrsize)
		{
			ecpg_log("ECPGget_desc on line %d: incorrect number of matches (indicator); %d don't fit into array of %ld\n",
					 fc_lineno, fc_ntuples, fc_data_var.ind_arrsize);
			ecpg_raise(fc_lineno, ECPG_TOO_MANY_MATCHES, ECPG_SQLSTATE_CARDINALITY_VIOLATION, NULL);
			va_end(fc_args);
			return false;
		}

		/* 如果需要，分配存储空间 */
		if (fc_data_var.ind_arrsize == 0 && fc_data_var.ind_value == NULL)
		{
			void	   *fc_mem = (void *) ecpg_auto_alloc(fc_data_var.ind_offset * fc_ntuples, fc_lineno);

			if (!fc_mem)
			{
				va_end(fc_args);
				return false;
			}
			*(void **) fc_data_var.ind_pointer = fc_mem;
			fc_data_var.ind_value = fc_mem;
		}

		for (fc_act_tuple = 0; fc_act_tuple < fc_ntuples; fc_act_tuple++)
		{
			if (!fc_get_int_item(fc_lineno, fc_data_var.ind_value, fc_data_var.ind_type, -PQgetisnull(fc_ECPGresult, fc_act_tuple, fc_index)))
			{
				va_end(fc_args);
				return false;
			}
			fc_data_var.ind_value = (char *) fc_data_var.ind_value + fc_data_var.ind_offset;
			ecpg_log("ECPGget_desc: INDICATOR[%d] = %d\n", fc_act_tuple, -PQgetisnull(fc_ECPGresult, fc_act_tuple, fc_index));
		}
	}
	fc_sqlca->sqlerrd[2] = fc_ntuples;
	va_end(fc_args);
	return true;
}

#undef RETURN_IF_NO_DATA

bool ECPGset_desc_header(int fc_lineno, const char *fc_desc_name, int fc_count)
{
	struct descriptor *fc_desc = ecpg_find_desc(fc_lineno, fc_desc_name);

	if (fc_desc == NULL)
		return false;
	fc_desc->count = fc_count;
	return true;
}

static void fc_set_desc_attr(struct descriptor_item *fc_desc_item, struct variable *fc_var,
			  char *fc_tobeinserted)
{
	if (fc_var->type != ECPGt_bytea)
		fc_desc_item->is_binary = false;

	else
	{
		struct ECPGgeneric_bytea *fc_variable =
		(struct ECPGgeneric_bytea *) (fc_var->value);

		fc_desc_item->is_binary = true;
		fc_desc_item->data_len = fc_variable->len;
	}

	ecpg_free(fc_desc_item->data); /* free() 会处理潜在的 NULL 值 */
	fc_desc_item->data = (char *) fc_tobeinserted;
}


bool ECPGset_desc(int fc_lineno, const char *fc_desc_name, int fc_index,...)
{
	va_list		fc_args;
	struct descriptor *fc_desc;
	struct descriptor_item *fc_desc_item;
	struct variable *fc_var;

	fc_desc = ecpg_find_desc(fc_lineno, fc_desc_name);
	if (fc_desc == NULL)
		return false;

	for (fc_desc_item = fc_desc->items; fc_desc_item; fc_desc_item = fc_desc_item->next)
	{
		if (fc_desc_item->num == fc_index)
			break;
	}

	if (fc_desc_item == NULL)
	{
		fc_desc_item = (struct descriptor_item *) ecpg_alloc(sizeof(*fc_desc_item), fc_lineno);
		if (!fc_desc_item)
			return false;
		fc_desc_item->num = fc_index;
		if (fc_desc->count < fc_index)
			fc_desc->count = fc_index;
		fc_desc_item->next = fc_desc->items;
		fc_desc->items = fc_desc_item;
	}

	if (!(fc_var = (struct variable *) ecpg_alloc(sizeof(struct variable), fc_lineno)))
		return false;

	va_start(fc_args, fc_index);

	for (;;)
	{
		enum ECPGdtype fc_itemtype;
		char	   *fc_tobeinserted = NULL;

		fc_itemtype = va_arg(fc_args, enum ECPGdtype);

		if (fc_itemtype == ECPGd_EODT)
			break;

		fc_var->type = va_arg(fc_args, enum ECPGttype);
		fc_var->pointer = va_arg(fc_args, char *);

		fc_var->varcharsize = va_arg(fc_args, long);
		fc_var->arrsize = va_arg(fc_args, long);
		fc_var->offset = va_arg(fc_args, long);

		if (fc_var->arrsize == 0 || fc_var->varcharsize == 0)
			fc_var->value = *((char **) (fc_var->pointer));
		else
			fc_var->value = fc_var->pointer;

		/*
		 * 负值用于表示没有给定界限的数组
		 */
		/* 重置为零 */
		if (fc_var->arrsize < 0)
			fc_var->arrsize = 0;
		if (fc_var->varcharsize < 0)
			fc_var->varcharsize = 0;

		fc_var->next = NULL;

		switch (fc_itemtype)
		{
			case ECPGd_data:
				{
					if (!ecpg_store_input(fc_lineno, true, fc_var, &fc_tobeinserted, false))
					{
						ecpg_free(fc_var);
						va_end(fc_args);
						return false;
					}

					fc_set_desc_attr(fc_desc_item, fc_var, fc_tobeinserted);
					fc_tobeinserted = NULL;
					break;
				}

			case ECPGd_indicator:
				fc_set_int_item(fc_lineno, &fc_desc_item->indicator, fc_var->pointer, fc_var->type);
				break;

			case ECPGd_length:
				fc_set_int_item(fc_lineno, &fc_desc_item->length, fc_var->pointer, fc_var->type);
				break;

			case ECPGd_precision:
				fc_set_int_item(fc_lineno, &fc_desc_item->precision, fc_var->pointer, fc_var->type);
				break;

			case ECPGd_scale:
				fc_set_int_item(fc_lineno, &fc_desc_item->scale, fc_var->pointer, fc_var->type);
				break;

			case ECPGd_type:
				fc_set_int_item(fc_lineno, &fc_desc_item->type, fc_var->pointer, fc_var->type);
				break;

			default:
				{
					char		fc_type_str[20];

					snprintf(fc_type_str, sizeof(fc_type_str), "%d", fc_itemtype);
					ecpg_raise(fc_lineno, ECPG_UNKNOWN_DESCRIPTOR_ITEM, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, fc_type_str);
					ecpg_free(fc_var);
					va_end(fc_args);
					return false;
				}
		}
	}
	ecpg_free(fc_var);
	va_end(fc_args);

	return true;
}

/* 释放描述符及其中的项目。 */
static void fc_descriptor_free(struct descriptor *fc_desc)
{
	struct descriptor_item *fc_desc_item;

	for (fc_desc_item = fc_desc->items; fc_desc_item;)
	{
		struct descriptor_item *fc_di;

		ecpg_free(fc_desc_item->data);
		fc_di = fc_desc_item;
		fc_desc_item = fc_desc_item->next;
		ecpg_free(fc_di);
	}

	ecpg_free(fc_desc->name);
	PQclear(fc_desc->result);
	ecpg_free(fc_desc);
}

bool ECPGdeallocate_desc(int fc_line, const char *fc_name)
{
	struct descriptor *fc_desc;
	struct descriptor *fc_prev;
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_line, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}

	ecpg_init_sqlca(fc_sqlca);
	for (fc_desc = fc_get_descriptors(), fc_prev = NULL; fc_desc; fc_prev = fc_desc, fc_desc = fc_desc->next)
	{
		if (strcmp(fc_name, fc_desc->name) == 0)
		{
			if (fc_prev)
				fc_prev->next = fc_desc->next;
			else
				fc_set_descriptors(fc_desc->next);
			fc_descriptor_free(fc_desc);
			return true;
		}
	}
	ecpg_raise(fc_line, ECPG_UNKNOWN_DESCRIPTOR, ECPG_SQLSTATE_INVALID_SQL_DESCRIPTOR_NAME, fc_name);
	return false;
}

#ifdef ENABLE_THREAD_SAFETY

/* 解除列表中所有描述符的分配 */
static void fc_descriptor_deallocate_all(struct descriptor *fc_list)
{
	while (fc_list)
	{
		struct descriptor *fc_next = fc_list->next;

		fc_descriptor_free(fc_list);
		fc_list = fc_next;
	}
}
#endif							/* 启用线程安全 */

bool ECPGallocate_desc(int fc_line, const char *fc_name)
{
	struct descriptor *fc_new;
	struct sqlca_t *fc_sqlca = ECPGget_sqlca();

	if (fc_sqlca == NULL)
	{
		ecpg_raise(fc_line, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}

	ecpg_init_sqlca(fc_sqlca);
	fc_new = (struct descriptor *) ecpg_alloc(sizeof(struct descriptor), fc_line);
	if (!fc_new)
		return false;
	fc_new->next = fc_get_descriptors();
	fc_new->name = ecpg_alloc(strlen(fc_name) + 1, fc_line);
	if (!fc_new->name)
	{
		ecpg_free(fc_new);
		return false;
	}
	fc_new->count = -1;
	fc_new->items = NULL;
	fc_new->result = PQmakeEmptyPGresult(NULL, 0);
	if (!fc_new->result)
	{
		ecpg_free(fc_new->name);
		ecpg_free(fc_new);
		ecpg_raise(fc_line, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}
	strcpy(fc_new->name, fc_name);
	fc_set_descriptors(fc_new);
	return true;
}

/* 在连接中查找具有名称的描述符。 */
struct descriptor *
ecpg_find_desc(int fc_line, const char *fc_name)
{
	struct descriptor *fc_desc;

	for (fc_desc = fc_get_descriptors(); fc_desc; fc_desc = fc_desc->next)
	{
		if (strcmp(fc_name, fc_desc->name) == 0)
			return fc_desc;
	}

	ecpg_raise(fc_line, ECPG_UNKNOWN_DESCRIPTOR, ECPG_SQLSTATE_INVALID_SQL_DESCRIPTOR_NAME, fc_name);
	return NULL;				/* 未找到 */
}

bool ECPGdescribe(int fc_line, int fc_compat, bool fc_input, const char *fc_connection_name, const char *fc_stmt_name,...)
{
	bool		fc_ret = false;
	struct connection *fc_con;
	struct prepared_statement *fc_prep;
	PGresult   *fc_res;
	va_list		fc_args;

	/* DESCRIBE INPUT 尚不支持 */
	if (fc_input)
	{
		ecpg_raise(fc_line, ECPG_UNSUPPORTED, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, "DESCRIBE INPUT");
		return fc_ret;
	}

	fc_con = ecpg_get_connection(fc_connection_name);
	if (!fc_con)
	{
		ecpg_raise(fc_line, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST,
				   fc_connection_name ? fc_connection_name : ecpg_gettext("NULL"));
		return fc_ret;
	}
	fc_prep = ecpg_find_prepared_statement(fc_stmt_name, fc_con, NULL);
	if (!fc_prep)
	{
		ecpg_raise(fc_line, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, fc_stmt_name);
		return fc_ret;
	}

	va_start(fc_args, fc_stmt_name);

	for (;;)
	{
		enum ECPGttype fc_type;
		void	   *fc_ptr;

		/* 变量类型 */
		fc_type = va_arg(fc_args, enum ECPGttype);

		if (fc_type == ECPGt_EORT)
			break;

		/* 其余的变量参数 */
		fc_ptr = va_arg(fc_args, void *);
		(void) va_arg(fc_args, long);	/* 跳过参数 */
		(void) va_arg(fc_args, long);
		(void) va_arg(fc_args, long);

		/* 变量指示符 */
		(void) va_arg(fc_args, enum ECPGttype);
		(void) va_arg(fc_args, void *);	/* 跳过参数 */
		(void) va_arg(fc_args, long);
		(void) va_arg(fc_args, long);
		(void) va_arg(fc_args, long);

		switch (fc_type)
		{
			case ECPGt_descriptor:
				{
					char	   *fc_name = fc_ptr;
					struct descriptor *fc_desc = ecpg_find_desc(fc_line, fc_name);

					if (fc_desc == NULL)
						break;

					fc_res = PQdescribePrepared(fc_con->connection, fc_stmt_name);
					if (!ecpg_check_PQresult(fc_res, fc_line, fc_con->connection, fc_compat))
						break;

					if (fc_desc->result != NULL)
						PQclear(fc_desc->result);

					fc_desc->result = fc_res;
					fc_ret = true;
					break;
				}
			case ECPGt_sqlda:
				{
					if (INFORMIX_MODE(fc_compat))
					{
						struct sqlda_compat **fc__sqlda = fc_ptr;
						struct sqlda_compat *fc_sqlda;

						fc_res = PQdescribePrepared(fc_con->connection, fc_stmt_name);
						if (!ecpg_check_PQresult(fc_res, fc_line, fc_con->connection, fc_compat))
							break;

						fc_sqlda = ecpg_build_compat_sqlda(fc_line, fc_res, -1, fc_compat);
						if (fc_sqlda)
						{
							struct sqlda_compat *fc_sqlda_old = *fc__sqlda;
							struct sqlda_compat *fc_sqlda_old1;

							while (fc_sqlda_old)
							{
								fc_sqlda_old1 = fc_sqlda_old->desc_next;
								free(fc_sqlda_old);
								fc_sqlda_old = fc_sqlda_old1;
							}

							*fc__sqlda = fc_sqlda;
							fc_ret = true;
						}

						PQclear(fc_res);
					}
					else
					{
						struct sqlda_struct **fc__sqlda = fc_ptr;
						struct sqlda_struct *fc_sqlda;

						fc_res = PQdescribePrepared(fc_con->connection, fc_stmt_name);
						if (!ecpg_check_PQresult(fc_res, fc_line, fc_con->connection, fc_compat))
							break;

						fc_sqlda = ecpg_build_native_sqlda(fc_line, fc_res, -1, fc_compat);
						if (fc_sqlda)
						{
							struct sqlda_struct *fc_sqlda_old = *fc__sqlda;
							struct sqlda_struct *fc_sqlda_old1;

							while (fc_sqlda_old)
							{
								fc_sqlda_old1 = fc_sqlda_old->desc_next;
								free(fc_sqlda_old);
								fc_sqlda_old = fc_sqlda_old1;
							}

							*fc__sqlda = fc_sqlda;
							fc_ret = true;
						}

						PQclear(fc_res);
					}
					break;
				}
			default:
				/* 不允许有其他内容 */
				;
		}
	}

	va_end(fc_args);

	return fc_ret;
}
